﻿<!DOCTYPE HTML>
<html lang="zh">
<head>
<title>动态库调用 - 语法 &amp; 使用 | AutoHotkey v2</title>
<meta name="description" content="The 动态库调用 function calls a function inside a DLL, such as a standard Windows API function." />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link href="../static/theme.css" rel="stylesheet" type="text/css" />
<script src="../static/content.js" type="text/javascript"></script>
<script type="text/javascript">$(function(){0<=window.navigator.userAgent.toLowerCase().indexOf("ucbrowser")&&CaoNiMaDeUc()})</script>
</head>
<body>

<h1>动态库调用</h1>

<p>调用 DLL 文件中的函数, 例如标准的 Windows API 函数.</p>

<pre class="Syntax">Result := <span class="func">动态库调用</span>("<span class="optional">DllFile\</span>Function" <span class="optional">, Type1, Arg1, Type2, Arg2, "Cdecl ReturnType"</span>)</pre>
<h2 id="Parameters">参数</h2>
<dl>

  <dt>[DllFile\]Function</dt>
  <dd>
      <p>类型: <a href="../Concepts.htm#strings">字符串</a>或<a href="../Concepts.htm#numbers">整数</a></p>
      <p>DLL 或 EXE 文件名后面跟着一个反斜杠和函数名. 例如: <code>&quot;MyDLL\MyFunction&quot;</code>(文件扩展名省略时默认为 &quot;.dll&quot;). 如果未指定绝对路径, 则假定 <em>DllFile</em> 在系统的 PATH 指定的路径或 <a href="../Variables.htm#WorkingDir">内_工作目录</a> 中.</p>
      <p id="std">当调用位于 User32.dll, Kernel32.dll, ComCtl32.dll 或 Gdi32.dll 中的函数时, 可以省略 <em>DllFile</em>. 例如, <code>&quot;User32\IsWindowVisible&quot;</code> 和 <code>&quot;IsWindowVisible&quot;</code> 产生相同的结果.</p>
      <p>如果使用指定的名称无法找到函数, 则自动附加 "W"(Unicode) 前缀. 例如, <code>"MessageBox"</code> 等同于 <code>"MessageBoxW"</code>.</p>
      <p>当 <em>重复 </em> 调用 DLL 时, 通过<a href="#load">预先加载此 DLL 文件</a>可以显著改善执行效率.</p>
      <p>此参数也可以仅是一个表示需调用函数的内存地址的整数. 如 <a href="#COM">COM</a> 和 <a href="CallbackCreate.htm">创建回调</a> 提供的地址.</p>
  </dd>

  <dt>Type1, Arg1</dt>
  <dd>
      <p>类型: <a href="../Concepts.htm#strings">字符串</a></p>
      <p>这样的每对数据表示需传递给函数的单个参数. 参数的个数没有限制. 关于 <em>类型</em>, 请参阅请参阅下面的<a href="#types">类型表</a>. 关于 <em>Arg</em>, 指定传递给函数的值.</p>
  </dd>

  <dt>Cdecl ReturnType</dt>
  <dd>
      <p>类型: <a href="../Concepts.htm#strings">字符串</a></p>
      <p id="cdecl">通常省略单词 <em>Cdecl</em>, 因为大多数函数使用标准调用约定而不是 &quot;C&quot; 调用约定(像 wsprintf 这样接受可变数目参数的函数在这点上是个例外). 注意, 不支持大多数面向对象的 C++ 函数使用的 <i>thiscall</i> 约定.</p>
      <p>如果此参数存在, 那么单词 <em>Cdecl</em> 应该在返回值类型前列出(如果有). 在单词间使用空格或 tab 分隔. 例如: <code>&quot;Cdecl Str&quot;</code>.</p>
      <p>因为在 64 位代码中不存在单独的 "C" 调用约定, 所以在 AutoHotkey 的 64 位版本中可以使用 <i>Cdecl</i> 但没有效果.</p>
      <p><em>ReturnType</em>: 如果函数返回 32 位的有符号整型(Int), BOOL, 或没有返回值, 则 <em>ReturnType</em> 可以省略. 否则, 需要指定下面<a href="#types">类型表</a>中参数类型的其中一个. 还支持<a href="#asterisk">星号后缀</a>.</p>
  </dd>

</dl>

<h2 id="Return_Value">返回值</h2>
<p>类型: <a href="../Concepts.htm#strings">字符串</a>或<a href="../Concepts.htm#numbers">整数</a></p>
<p>动态库调用 返回 <em>Function</em> 返回的实际值. 如果 <em>Function</em> 属于不返回值的类型, 则结果为指定返回类型的未定义值(默认为整数).</p>

<h2 id="types">参数和返回值的类型</h2>
<table class="info">
<tr id="str">
<td>Str</td>
<td><p>例如 &quot;Blue&quot; 或 MyVar 这样的字符串. 如果被调用函数修改了字符串且此参数是个裸变量, 那么其内容将被更新. 例如, 后面的调用将把 <em>MyVar</em> 的内容转换成大写: <code>动态库调用(&quot;CharUpper&quot;, &quot;Str&quot;, <i>MyVar</i>)</code>.</p>
  <p>如果函数的设计存储长度大于参数的输入值(或者参数仅用于输出), 建议使用 <a href="BufferAlloc.htm">创建缓冲区</a> 分配缓冲, <a href="#ptr">Ptr</a> 类型传递缓冲, <a href="StrGet.htm">获取字符串</a> 在函数返回后检索字符串.</p>
  <p>否则, 请在调用函数之前确保变量足够大. 这可以通过调用 <code><a href="VarSetStrCapacity.htm">VarSetStrCapacity</a>(MyVar, 123)</code> 来实现, 其中 123 是 <em>MyVar</em> 肯定能够容纳的 16 位单位(通俗地讲就是字符) 的数量. 如果变量返回时不是以空终止符结尾的, 则会显示一条错误消息, 并且由于内存可能由于缓冲溢出而损坏导致程序退出. 这通常表明变量的容量不足.</p>
  <p></p>
  <p><em>Str</em> 参数不能是计算结果为数字(例如 <code>i+1</code>) 这样的<a href="../Variables.htm#Expressions">表达式</a>. 如果如此, 则不会调用函数并抛出<a href="Catch.htm#RuntimeErrors">异常</a>.</p>
  <p id="strp">很少使用的 <a href="#asterisk">Str*</a> 参数类型传递包含字符串地址的临时变量的地址. 如果函数将新地址写入临时变量, 则将新字符串复制到脚本变量(如果已传递). 它可以用在期望 &quot;TCHAR **&quot; 或 &quot;LPTSTR *&quot; 类型参数的函数中. 但是, 如果函数分配了内存并希望调用者释放它(例如通过调用 <a href="https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cotaskmemfree">CoTaskMemFree</a>), 则必须改用 <code>Ptr*</code> 参数类型.</p>
  <p class="note"><strong>注意</strong>: 在将字符串传递给函数时, 请注意函数所期望的字符串的 <a href="../Compat.htm#DllCall"><i>类型</i>(类型).</p></td>
</tr>
<tr>
  <td><span id="wstr"></span>WStr</td>
  <td>由于 AutoHotkey 原生使用 UTF-16, 因此 WStr(宽字符串) 等同于 Str.</td>
</tr>
<tr>
  <td><span id="astr"></span>AStr</td>
  <td>
    <p>AStr 导致输入值自动转换为 ANSI. 由于用于此转换的临时内存仅足够容纳转换后的输入字符串, 因此函数写入其的任何值都将被丢弃. 要接收 ANSI 字符串作为输出参数, 请遵循以下示例:</p>
<pre>buf := 创建缓冲区(<i>length</i>)  <em>; 分配临时缓冲.</em>
动态库调用("<i>Function</i>", "ptr", buf)  <em>; 传递缓冲给函数.</em>
str := 获取字符串(buf, "cp0")  <em>; 从缓冲接收 ANSI 字符串.</em>
</pre>
    <p>还支持很少使用的 <a href="#asterisk">AStr*</a> 参数类型, 其行为与 <a href="#strp">Str*</a> 类型类似, 不同之处在于, 所有新字符串都将从 ANSI 转换为原生格式, UTF-16.</p>
    <p>有关等效的 Win32 类型和其他详细信息, 请参阅<a href="../Compat.htm#DllCall">脚本兼容性</a>.</p>
  </td>
</tr>
<tr>
  <td>Int64</td>
  <td>64 位整数, 其范围是 -9223372036854775808(-0x8000000000000000) 到 9223372036854775807(0x7FFFFFFFFFFFFFFF).</td>
</tr>
<tr id="Int">
  <td>Int</td>
  <td><p>32 位整数(最常用的整数类型), 其范围是 -2147483648(-0x80000000) 到 2147483647(0x7FFFFFFF). Int 有时也被称为 &quot;Long&quot;.</p>
    <p>在函数期望 BOOL 类型的参数中也应该使用 Int(BOOL 值应该是 1 或 0).</p>
    <p><a href="#unsigned">无符号</a>的 Int(UInt) 也经常使用, 例如用作 DWORD.</p></td>
</tr>
<tr>
  <td>Short</td>
  <td>16 位整数, 其范围是 -32768(-0x8000) 到 32767(0x7FFF). <a href="#unsigned">无符号</a> Short(UShort) 能用于期望 BYTE 的函数中.</td>
</tr>
<tr>
  <td>Char</td>
  <td>8 位整数, 其范围是 -128(-0x80) 到 127(0x7F). <a href="#unsigned">无符号</a> character(UChar) 能用于期望 WORD 的函数中.</td>
</tr>
<tr>
  <td>浮点数</td>
  <td>32 位浮点数, 具有 6 位精度.</td>
</tr>
<tr>
  <td>Double</td>
  <td>64 位浮点数, 具有 15 位精度.</td>
</tr>
<tr id="ptr">
  <td>Ptr</td>
  <td><p><a href="../Variables.htm#PtrSize">指针大小</a>的整数, 等同于 Int 或 Int64, 具体取决于运行脚本的 exe 是 32 位还是 64 位. <i>Ptr</i> 应该用于指向数组或结构(如 RECT* 或 LPPOINT) 以及几乎所有句柄(如 HWND, HBRUSH 或 HBITMAP) 的指针. 如果参数是指向简单数值的指针( 如 LPDWORD 或 int*), 通常应该使用 * 或 P 后缀代替 &quot;Ptr&quot;.</p>
  <p>如果将对象传递给 Ptr 参数, 则使用该对象的 <code>Ptr</code> 属性的值. 如果不存在此类属性, 则抛出异常. 通常, 该对象是 <a href="BufferAlloc.htm">创建缓冲区</a> 创建的 <a href="../objects/Buffer.htm">Buffer</a> 对象.</p>
  <p>如果将对象传递给 Ptr* 参数, 则在调用之前将检索对象的 <code>Ptr</code> 属性值, 并将包含该值的临时变量的地址传递给函数. 函数返回后, 新值将分配回该对象的 <code>Ptr</code> 属性.</p>
  <p><i>Ptr</i> 也可以和 * 或 P 后缀一起使用; 此时它应该用在通过 LPVOID* 或类似类型输出指针的函数中.</p>
  <p><i>UPtr</i> 也是有效的, 但仅适用于 32 位版本中的无符号整数, 因为 AutoHotkey 不支持无符号的 64 位整数.</p>
  <p class="note"><strong>注意</strong>: 要传递 <strong>NULL</strong> 句柄或指针, 请传递整数 0.</p></td>
</tr>
<tr id="asterisk">
  <td>* 或 P<br>
    (后缀)</td>
  <td><p>在上述任何类型上附加一个星号(星号前可选的空格), 以表示传递参数的地址而不是值本身(必须将被调用的函数设计为接受该参数). 由于此类参数的值可能会被函数修改, 因此每当将裸变量作为参数传递时, 该变量的内容就会被更新. 例如, 以下调用将通过地址将 MyVar 的内容传递给 MyFunction, 但还将更新 MyVar 以反映 MyFunction 对它所做的任何更改: <code>动态库调用(&quot;MyDll\MyFunction&quot;, &quot;Int*&quot;, MyVar)</code>.</p>
    <p>通常, 星号可用于参数类型或返回类型以 &quot;LP&quot; 开头的函数. 最常见的示例是 LPDWORD, 它是指向 DWORD 的指针. 由于 DWORD 是 32 位无符号整数, 因此请使用 &quot;UInt*&quot; 或 &quot;UIntP&quot; 表示 LPDWORD. 星号不能用于字符串类型(如 LPTSTR), 指向结构的指针(如 LPRECT) 或数组; 对于这些, 应使用 <a href="#str">&quot;Str&quot;</a> 或 &quot;Ptr&quot;, 具体取决于您传递变量还是变量地址.</p>
    <p class="note"><strong>注意</strong>: &quot;Char*&quot; 与 <a href="#str">&quot;Str&quot;</a> 不同, 因为 &quot;Char*&quot; 传递一个 8 位数字的地址, 而 <a href="#str">&quot;Str&quot;</a> 传递一系列字符的地址, 该字符可以是 16 位(Unicode) 或 8 位("AStr"), 具体取决于 AutoHotkey 的版本. 同样, &quot;UInt*&quot; 传递的是 32 位数字的地址, 因此如果函数期望值的数组或大于 32 位的结构, 则不应使用 "UInt*".</p>
    <p>由于 AutoHotkey 中的变量没有固定类型, 因此传递给函数的地址指向临时内存, 而不是变量本身. 不必在变量上调用 <a href="VarSetStrCapacity.htm">VarSetStrCapacity</a>, 因为 动态库调用 将在函数返回后正确更新它.</p>
    </td>
</tr>
<tr id="unsigned">
  <td>U (前缀)</td>
  <td><p>在上述任一整数类型加上字母 U 前缀, 以将其解释为无符号整数(UInt64, UInt, UShort 和 UChar). 严格来说, 这仅对返回值和<a href="#asterisk">星号变量</a>才是必需的, 因为按值传递的参数是无符号的还是有符号的(Int64 除外) 都无关紧要.</p>
    <p>如果为无符号参数指定了负整数, 则该整数将被转换到无符号域中。 例如, 当 -1 作为 UInt 发送时, 它将被转换成 0xFFFFFFFF.</p>
    <p>不支持函数产生的 <em>无符号</em> 64 位整数. 因此, 要使用大于或等于 0x8000000000000000 的数字, 请省略 U 前缀并将从函数接收的任何负值解释为大整数. 例如, 如果将函数设计为产生 UInt64, 则将函数产生的 -1(Int64 类型), 实际上将产生 0xFFFFFFFFFFFFFFFF.</p>
    </td>
</tr>
<tr id="HRESULT">
  <td>HRESULT</td>
  <td>
    <p>32 位整数. 这通常与 COM 函数一起使用, 并且仅作为返回类型有效, 没有任何前缀或后缀. 错误值(由 <a href="https://docs.microsoft.com/en-us/windows/win32/api/winerror/nf-winerror-failed">FAILED macro</a> 定义) 永远不会返回; 而是抛出一个异常, 并将 <code>Extra</code> 设置为十六进制错误代码. 因此, 成功时, 返回值范围是 0 到 2147483647.</p>
    <p>HRESULT 是 <a href="ComCall.htm">组件调用</a> 的默认返回类型.</p>
  </td>
</tr>
</table>

<h2 id="error">错误</h2>
<p>动态库调用 会在下列任何一种情况下抛出<a href="Catch.htm#RuntimeErrors">异常</a>:</p>
<ul>
  <li>使用 <a href="#HRESULT">HRESULT</a> 返回类型, 函数返回一个错误值(由 <a href="https://docs.microsoft.com/en-us/windows/win32/api/winerror/nf-winerror-failed">FAILED macro</a> 定义). <code>异常.Extra</code> 包含十六进制错误码.</li>
  <li><em>[DllFile\]Function</em> 参数是一个浮点数. 需要字符串或正整数.</li>
  <li><a href="#types">返回的类型</a>或指定的<a href="#types">参数的类型</a>无效.</li>
  <li>参数传递了一个意外类型的值. 例如, 将计算结果为数字的<a href="../Variables.htm#Expressions">表达式</a>传递给字符串(<a href="#str">str</a>) 参数, 将非数字字符串传递给数值参数, 或者将对象传递给 <em>非</em> <a href="#ptr">Ptr</a> 类型的参数.</li>
  <li>无法访问或加载指定的 <em>DllFile</em>. 如果没有明确指明 <em>DllFile</em> 的路径, 则该文件必须存在于系统的 PATH 或 <a href="../Variables.htm#WorkingDir">内_工作目录</a> 中. 如果用户没有访问文件的权限, 或者 AutoHotkey 是 32 位而 DLL 是 64 位(反之亦然), 也都可能发生此错误.</li>
  <li>在 DLL 中找不到指定的函数.</li>
  <li>该函数已被调用, 但由于致命异常而中止. <code>异常.Extra</code> 包含异常代码. 例如, 0xC0000005 表示 &quot;访问冲突&quot;. 在这种情况下, 线程将中止(如果没有使用 <a href="Try.htm">try</a> 的话), 但是仍将更新所有<a href="#asterisk">星号变量</a>. 致命异常的一个例子是取消引用无效指针(如 NULL(0)). 由于 <a href="#cdecl">Cdecl</a> 函数永远不会产生下一段中描述的错误, 因此当传递给它的参数太少时, 它可能会生成异常.</li>
  <li>该函数已被调用, 但传递了太多或太少的参数. <code>异常.Extra</code> 包含参数列表不正确的字节数. 如果为正, 则传递了太多的参数(或参数太大), 或者调用需要 <a href="#cdecl">CDecl</a>. 如果为负, 则传递的参数太少. 应该纠正这种情况, 以确保函数的可靠运行. 出现此错误也可能表示抛出了异常. 请注意, 由于使用 x64 调用约定, 因此 64 位版本永远不会引发此错误.</li>
</ul>

<h2 id="except">原生异常和 内_错误代码</h2>
<p>尽管有内置的异常处理机制, 仍可能因使用 动态库调用 使脚本崩溃. 当函数不直接生成异常但产生不适当的数据时, 如无效的指针或未终止的字符串, 可能会发生这种情况. 如果脚本向其传递了不合适的值, 如无效的指针或容量不足的 <a href="#str">&quot;str&quot;</a>, 则可能不是函数的错误. 当脚本指定了不适当的参数类型或返回类型时, 如声明函数产生的普通整数是<a href="#asterisk">星号变量</a>或 <a href="#str">str</a>, 脚本也可能崩溃.</p>
<p>内置变量 <a href="../Variables.htm#LastError">内_错误代码</a> 包含操作系统的 GetLastError() 函数的结果.</p>

<h2 id="load">性能</h2>
<p>当需要重复调用一个 DLL 时, 预先明确装载它可以显著提高执行效率(<em>对于<a href="#std">标准的 DLL</a> 文件如 User32 这是没有必要的, 因为它是常驻的</em>). 这种做法避免了 动态库调用 每次都需要在内部调用 LoadLibrary 和 FreeLibrary. 例如:</p>
<pre>hModule := 动态库调用(&quot;<strong>LoadLibrary</strong>&quot;, &quot;Str&quot;, &quot;MyFunctions.dll&quot;, &quot;Ptr&quot;)  <em>; 避免在循环中使用 动态库调用 来加载库.</em>
遍历文件, "C:\My Documents\*.*", "R"
    result := 动态库调用(&quot;MyFunctions\BackupFile&quot;, &quot;Str&quot;, 内_遍历文件路径)
动态库调用(&quot;<strong>FreeLibrary</strong>&quot;, &quot;Ptr&quot;, hModule)  <em>; 为了释放内存, 在使用 DLL 后需要进行卸载.</em></pre>
<p>通过预先查找函数的地址甚至可以获得更快的性能. 例如:</p>
<pre><em>; 在下面的示例中, 如果 DLL 还没有装载, 使用 LoadLibrary 代替 GetModuleHandle.</em>
<strong>MulDivProc</strong> := 动态库调用(&quot;GetProcAddress&quot;, &quot;Ptr&quot;, 动态库调用(&quot;GetModuleHandle&quot;, &quot;Str&quot;, &quot;<strong>kernel32</strong>&quot;, &quot;Ptr&quot;), &quot;AStr&quot;, &quot;<strong>MulDiv</strong>&quot;, &quot;Ptr&quot;)
循环 500
    动态库调用(<strong>MulDivProc</strong>, &quot;Int&quot;, 3, &quot;Int&quot;, 4, &quot;Int&quot;, 3)</pre>
<p>如果 动态库调用 的首个参数是原义的字符串如 <code>"MulDiv"</code> 并且包含了函数的 DLL 在脚本开始前已正常装载了, 或者已成功通过 <a href="_DllLoad.htm">#DllLoad</a> 加载, 那么此字符串会自动被解析为函数地址. 这种内置的优化比上面显示的示例更有效.</p>
<p>最后, 当将字符串变量传递给不会改变字符串长度的函数时, 通过将变量按地址传递(例如 <code>字符串指针(MyVar)</code>), 而不是以 "<a href="#str">str</a>" 的形式传递, 可以提高性能(特别是当字符串很长时). 后面的例子把字符串转换成大写: <code>动态库调用("CharUpper", "<strong>Ptr</strong>", 字符串指针(MyVar), "Ptr")</code>.</p>

<h2 id="struct">结构和数组</h2>
<p>结构是内存中连续存储的 <em>成员</em>(空间) 的集合. 大多数成员倾向于是整数.</p>
<p>接受结构(或内存块数组) 地址的函数可以通过某种方式分配内存并将内存地址传递给函数来调用. <a href="BufferAlloc.htm">创建缓冲区</a> 函数和 <a href="../objects/Buffer.htm">Buffer</a> 对象被推荐用于此目的. 一般使用以下步骤:</p>
<p>1) 调用 <code>MyStruct := 创建缓冲区(123, 0)</code> 来分配一个缓冲来保存结构体的数据. 将 <code>123</code> 替换为至少与结构大小一样大的数字, 以字节为单位. 指定零作为最后一个参数是可选的; 它将所有成员初始化为二进制零, 这通常用于避免在下一步中频繁调用 置数值.</p>
<p>2) 如果目标函数使用结构中的初始值, 请调用 <code><a href="NumPut.htm">置数值</a>("UInt", 123, MyStruct, 4)</code>初始化任何不为零的成员. 将 <code>123</code> 替换为要放入目标成员的整数(或指定 <code>字符串指针(Var)</code> 来存储字符串的地址). 将 <code>4</code> 替换为目标成员的偏移量(有关 &quot;offset&quot; 的说明, 请参阅步骤 #4). 将 <code>"UInt"</code> 替换为适当的类型, 如果成员是指针或句柄, 则如 <code>"Ptr"</code>.</p>
<p>3) 调用目标函数, 将 <em>MyStruct</em> 作为 Ptr 参数传递. 例如, <code>动态库调用("MyDll\MyFunc", "Ptr", MyStruct)</code>. 该函数将检查和/或更改一些成员. 动态库调用 会自动使用缓冲的地址, 通常使用 <code>MyStruct.Ptr</code> 来检索.</p>
<p>4) 使用 <code>MyInteger := <a href="NumGet.htm">获取数值</a>(MyStruct, 4, "UInt")</code>从结构中检索任何所需的整数. 将 <code>4</code> 替换为结构中目标构件的偏移量. 第一个成员的偏移量始终为 0. 第二个成员的偏移量为 0 加第一个成员的大小(通常为 4). 第二个成员之后的成员是前一个成员的偏移量加上前一个成员的大小. 大多数成员 -- 如 DWORD, Int 和<a href="#Int">其他类型的 32 位整数</a> -- 的大小为 4 个字节. 将 <code>"UInt"</code> 替换为适当的类型, 如果成员是指针或句柄, 则将其省略.</p>
<p>有关实际用法, 请参阅 <a href="#ExStruct">Structure 示例</a>.</p>

<h2 id="limits">已知限制</h2>
<p>当一个变量的字符串地址(例如 <code>字符串指针(MyVar)</code>) 被传递给一个函数, 并且该函数改变了变量内容的长度时, 那么后面使用这个变量可能出现错误. 要解决此问题, 有下面几种方法: 1) 将 <em>MyVar</em> 作为 <a href="#str">"Str"</a> 参数传递, 而不是 Ptr/地址; 2) 在调用 动态库调用 后, 调用 <code><a href="VarSetStrCapacity.htm#neg1">VarSetStrCapacity</a>(MyVar, -1)</code> 来更新变量的内部存储长度.</p>
<p>由函数保存到变量中的任何二进制零都可以作为终止符, 防止大多数内置函数访问或更改零右边的所有数据. 但是, 这样的数据可以使用 <a href="StrPtr.htm">字符串指针</a> 检索字符串的地址, 并将其传递给其他函数, 例如 <a href="NumPut.htm">置数值</a>, <a href="NumGet.htm">获取数值</a>, <a href="StrGet.htm">获取字符串</a>, <a href="StrPut.htm">置字符串</a>, 以及 动态库调用 本身进行操作.</p>
<p>当一个字符串传递给一个返回此字符串地址的函数后, 此函数可能会在与预期不同的内存地址中返回相同的字符串. 例如在编程语言中调用 <code>CharLower(CharUpper(MyVar))</code> 将把 <em>MyVar</em> 的内容转换成小写形式, 但当使用 动态库调用 进行相同操作时, 在后面的调用操作后 <em>MyVar</em> 将是小写的, 因为 CharLower 对一个内容与 <em>MyVar</em> 相同而地址不同的临时字符串进行操作:</p>
<pre>变量 :=  "ABC"
result := 动态库调用(&quot;CharLower&quot;, &quot;<strong><u>Str</u></strong>&quot;, 动态库调用(&quot;CharUpper&quot;, &quot;Str&quot;, MyVar, &quot;<strong><u>Str</u></strong>&quot;), &quot;Str&quot;)</pre>
<p>要变通解决此问题, 请把上面两个带下划线的 &quot;Str&quot; 值修改成 Ptr. 这种情况说明了 CharUpper 的返回值为纯地址并作为整数传递给 CharLower.</p>
<p>处理字符串时可能遇到某些限制. 更多详情, 请参阅<a href="../Compat.htm#DllCall">脚本兼容性</a>.</p>

<h2 id="COM">组件对象模型(COM)</h2>
<p>在 VBScript 和其他类似语言中可访问的 COM 对象在 AutoHotkey 中一般可以通过 <a href="ComObjCreate.htm">组件对象创建</a>, <a href="ComObjGet.htm">组件对象获取</a> 或 <a href="ComObjActive.htm">组件对象激活</a> 以及内置的<a href="../Objects.htm#Usage_Objects">对象语法</a>进行访问.</p>
不支持 <a href="http://msdn.microsoft.com/en-us/library/ms221608.aspx">IDispatch</a> 的 COM 对象可以通过从对象接口的虚拟函数表中获取函数的地址用于 动态库调用 中. 有关详情, 请参阅下面的<a href="#ExTaskbar">示例</a>. 但是, 通常最好使用 <a href="ComCall.htm">组件调用</a> 来简化此过程.</p>

<h2 id="dotnet">.NET 框架</h2>
<p>.NET Framework 库由被称为公共语言运行时(CLR) 的 "虚拟机" 来执行. 在这种情况下, .NET DLL 文件的格式与常规 DLL 文件的格式不同, 并且通常不包含 动态库调用 能够调用的任何函数.</p>
<p>但是, AutoHotkey 可以通过 <a href="https://docs.microsoft.com/en-us/dotnet/standard/native-interop/com-callable-wrapper">COM 可调用包装器</a>来使用 CLR. 除非该库也已注册为通用 COM 组件, 否则必须先通过 动态库调用 手动初始化 CLR 本身. 有关详情, 请参阅 <a href="https://www.autohotkey.com/forum/topic26191.html">.NET Framework Interop</a>(其当前使用的 动态库调用 与 AutoHotkey v2 不兼容).</p>

<h2 id="Related">相关</h2>
<p><a href="../Compat.htm#DllCall">脚本兼容性</a>, <a href="BufferAlloc.htm">创建缓冲区</a>, <a href="../objects/Buffer.htm">缓冲对象</a>, <a href="ComCall.htm">组件调用</a>, <a href="PostMessage.htm">投递消息</a>, <a href="OnMessage.htm">在收到消息时</a>, <a href="CallbackCreate.htm">创建回调</a>, <a href="Run.htm">运行</a>, <a href="VarSetStrCapacity.htm">VarSetStrCapacity</a>, <a href="../Functions.htm">函数</a>, <a href="SysGet.htm">获取系统属性</a>, <a href="_DllLoad.htm">#DllLoad</a>, <a href="https://docs.microsoft.com/en-au/windows/win32/apiindex/windows-api-list">Windows API Index</a></p>

<h2 id="Examples">示例</h2>
<div class="ex" id="ExMessageBox">
<p><a href="#ExMessageBox">#1</a>: 调用 Windows API 函数 &quot;MessageBox&quot; 并报告用户按下了哪个按钮.</p>
<pre>WhichButton := 动态库调用(&quot;MessageBox&quot;, &quot;Int&quot;, 0, &quot;Str&quot;, &quot;Press Yes or No&quot;, &quot;Str&quot;, &quot;Title of box&quot;, &quot;Int&quot;, 4)
信息框 "You pressed button #" WhichButton</pre>
</div>

<div class="ex" id="ExWallpaper">
<p><a href="#ExWallpaper">#2</a>: 将桌面墙纸更改为指定的位图(.bmp) 文件.</p>
<pre>动态库调用(&quot;SystemParametersInfo&quot;, &quot;UInt&quot;, 0x14, &quot;UInt&quot;, 0, &quot;Str&quot;, 内_系统目录 <strong>.</strong> &quot;\winnt.bmp&quot;, &quot;UInt&quot;, 2)</pre>
</div>

<div class="ex" id="ExIsWindowVisible">
<p><a href="#ExIsWindowVisible">#3</a>: 调用 API 函数 &quot;IsWindowVisible&quot; 来判断记事本窗口是否可见.</p>
<pre>检测隐藏窗口 True
如果 not 动态库调用(&quot;IsWindowVisible&quot;, &quot;Ptr&quot;, 窗口存在(&quot;无标题 - 记事本&quot;))  <em>; 窗口存在 返回 HWND.</em>
    信息框 "The window is not visible."</pre>
</div>

<div class="ex" id="ExWsprintf">
<p><a href="#ExWsprintf">#4</a>: 调用 API 的 wsprintf() 函数来给数字 432 加上前导零以填充到 10 个字符的长度(0000000432).</p>
<pre>ZeroPaddedNumber := 创建缓冲区(20)  <em>; 确保缓冲足够大以便容纳新的字符串.</em>
动态库调用(&quot;wsprintf&quot;, &quot;Ptr&quot;, ZeroPaddedNumber, &quot;Str&quot;, &quot;%010d&quot;, &quot;Int&quot;, 432, &quot;Cdecl&quot;)  <em>; 需要 Cdecl 调用约定.</em>
信息框 获取字符串(ZeroPaddedNumber)

<em>; 此外, 使用 <a href="Format.htm">格式化字符串</a> 函数配合 0 标志可以达到同样的效果:</em>
信息框 格式化字符串("{:010}", 432)
</pre>
</div>

<div class="ex" id="ExQPC">
<p><a href="#ExQPC">#5</a>: 演示 QueryPerformanceCounter(), 它提供了比 <a href="../Variables.htm#TickCount">内_系统运行时间</a> 的 10ms 更高的精确度.</p>
<pre>动态库调用(&quot;QueryPerformanceFrequency&quot;, &quot;Int64*&quot;, freq := 0)
动态库调用(&quot;QueryPerformanceCounter&quot;, &quot;Int64*&quot;, CounterBefore := 0)
等待 1000
动态库调用(&quot;QueryPerformanceCounter&quot;, &quot;Int64*&quot;, CounterAfter := 0)
信息框 &quot;Elapsed QPC time is &quot; . (CounterAfter - CounterBefore) / freq * 1000 &quot; ms&quot;</pre>
</div>

<div class="ex" id="ExMouseSpeed">
<p><a href="#ExMouseSpeed">#6</a>: 这是个临时减慢鼠标移动速度的热键, 这样有助于准确定位. 按住 <kbd>F1</kbd> 来降低鼠标速度. 释放后则恢复原来的速度.</p>
<pre>
F1::
F1 up::
{
    静态 SPI_GETMOUSESPEED := 0x70
    静态 SPI_SETMOUSESPEED := 0x71
    静态 OrigMouseSpeed := 0

    判断 ThisHotkey
    {
    为 "F1":
        <em>; 获取鼠标当前的速度以便稍后恢复:</em>
        动态库调用("SystemParametersInfo", "UInt", SPI_GETMOUSESPEED, "UInt", 0, "Ptr*", OrigMouseSpeed, "UInt", 0)
        <em>; 现在在倒数第二个参数中设置较低的速度 (范围为 1-20, 10 是默认值):</em>
        动态库调用("SystemParametersInfo", "UInt", SPI_SETMOUSESPEED, "UInt", 0, "Ptr", 3, "UInt", 0)
        等待按键 "F1"  <em>; 这里避免了由于键盘的重复特性导致再次执行 动态库调用.</em>

    为 "F1 up":
        动态库调用("SystemParametersInfo", "UInt", SPI_SETMOUSESPEED, "UInt", 0, "Ptr", OrigMouseSpeed, "UInt", 0)  <em>; 恢复原始速度.</em>
    }
}</pre>
</div>

<div class="ex" id="ExWatchScrollBar">
<p><a href="#ExWatchScrollBar">#7</a>: 监视活动窗口并显示其焦点控件中的垂直滚动条的位置(实时更新).</p>
<pre>设置定时器 "WatchScrollBar", 100

WatchScrollBar()
{
    try FocusedHwnd := 获取焦点控件("A")
    如果 !FocusedHwnd  <em>; 没有焦点控件.</em>
        返回
    <em>; 在工具提示中显示垂直或水平滚动条的位置:</em>
    工具提示(动态库调用("GetScrollPos", "Ptr", FocusedHwnd, "Int", 1))  <em>;  最后一个参数 1 表示 SB_VERT, 而 0 表示 SB_HORZ.</em>
}</pre>
</div>

<div class="ex" id="ExFile">
<p><a href="#ExFile">#8</a>: 这是个可运行脚本, 它写入一些文本到文件, 然后从文件读取回内存. 在同时读取或写入多个文件时, 使用这种方法可以改善性能. 此外, 使用 <a href="FileOpen.htm">打开文件</a> 可以实现<a href="FileOpen.htm#ExWriteRead">同样的目的</a>.</p>
<pre>FileName := 文件选择框("S16",, "Create a new file:")
如果 FileName = ""
    返回
GENERIC_WRITE := 0x40000000  <em>; 以写入而不是读取的方式打开文件.</em>
CREATE_ALWAYS := 2  <em>; 创建新文件(覆盖任何现有的文件).</em>
hFile := 动态库调用(&quot;CreateFile&quot;, &quot;Str&quot;, FileName, &quot;UInt&quot;, GENERIC_WRITE, &quot;UInt&quot;, 0, &quot;Ptr&quot;, 0, &quot;UInt&quot;, CREATE_ALWAYS, &quot;UInt&quot;, 0, &quot;Ptr&quot;, 0, &quot;Ptr&quot;)
如果 !hFile
{
    信息框 "Can't open '" FileName "' 遍历 writing."
    返回
}
TestString := "This is a test 字符串.`r`n"  <em>; 通过这种方式写入内容到文件时, 要使用 `r`n 而不是 `n 来开始新行.</em>
StrSize := 字符串长度(TestString) * 2
动态库调用("WriteFile", "Ptr", hFile, "Str", TestString, "UInt", StrSize, "UIntP", BytesActuallyWritten := 0, "Ptr", 0)
动态库调用(&quot;CloseHandle&quot;, &quot;Ptr&quot;, hFile)  <em>; 关闭文件.</em>

<em>; 现在已经把内容写入文件了, 重新把它们读取回内存中.</em>
GENERIC_READ := 0x80000000  <em>; 以读取而不是写入的方式来打开文件.</em>
OPEN_EXISTING := 3  <em>; 此标志表示要打开的文件必须已经存在.</em>
FILE_SHARE_READ := 0x1 <em>; 这个和下一个标志表示其他进程是否可以打开我们已经打开的文件.</em>
FILE_SHARE_WRITE := 0x2
hFile := 动态库调用(&quot;CreateFile&quot;, &quot;Str&quot;, FileName, &quot;UInt&quot;, GENERIC_READ, &quot;UInt&quot;, FILE_SHARE_READ|FILE_SHARE_WRITE, &quot;Ptr&quot;, 0, &quot;UInt&quot;, OPEN_EXISTING, &quot;UInt&quot;, 0, &quot;Ptr&quot;, 0)
如果 !hFile
{
    信息框 "Can't open '" FileName "' 遍历 reading."
    返回
}
<em>; 为要读取的字符串分配一块内存:</em>
Buf := 创建缓冲区(StrSize)
动态库调用(&quot;ReadFile&quot;, &quot;Ptr&quot;, hFile, &quot;Ptr&quot;, Buf, &quot;UInt&quot;, Buf.Size, &quot;UIntP&quot;, BytesActuallyRead := 0, &quot;Ptr&quot;, 0)
动态库调用(&quot;CloseHandle&quot;, &quot;Ptr&quot;, hFile)  <em>; 关闭文件.</em>
信息框 "The following 字符串 was read from the file: " 获取字符串(Buf)</pre>
</div>

<div class="ex" id="ExHideCursor">
<p><a href="#ExHideCursor">#9</a>: 当您按下 <kbd>Win</kbd>+<kbd>C</kbd> 时, 隐藏鼠标光标. 稍后若要显示光标, 请再次按下热键.</p>
<pre>在退出时 (*) =&gt; SystemCursor("Show")  <em>; 确保到脚本退出时鼠标光标是显示的.</em>

#c::SystemCursor("Toggle")  <em>; Win+C 热键用来切换鼠标光标的显示和隐藏.</em>

SystemCursor(cmd)  <em>; cmd = "Show|Hide|Toggle|Reload"</em>
{
    静态 visible := true, c := 映射()
    静态 sys_cursors := [32512, 32513, 32514, 32515, 32516, 32642
                         , 32643, 32644, 32645, 32646, 32648, 32649, 32650]
    如果 (cmd = "Reload" or !c.Count)  <em>; 在请求或首次调用时进行重载.</em>
    {
        遍历 i, id in sys_cursors
        {
            h_cursor  := 动态库调用("LoadCursor", "Ptr", 0, "Ptr", id)
            h_default := 动态库调用("CopyImage", "Ptr", h_cursor, "UInt", 2
                , "Int", 0, "Int", 0, "UInt", 0)
            h_blank   := 动态库调用("CreateCursor", "Ptr", 0, "Int", 0, "Int", 0
                , "Int", 32, "Int", 32
                , "Ptr", 创建缓冲区(32*4, 0xFF)
                , "Ptr", 创建缓冲区(32*4, 0))
            c[id] := {default: h_default, blank: h_blank}
        }
    }
    判断 cmd
    {
      为 "Show": visible := true
      为 "Hide": visible := false
      为 "Toggle": visible := !visible
      default: 返回
    }
    遍历 id, handles in c
    {
        h_cursor := 动态库调用("CopyImage"
            , "Ptr", visible ? handles.default : handles.blank
            , "UInt", 2, "Int", 0, "Int", 0, "UInt", 0)
        动态库调用("SetSystemCursor", "Ptr", h_cursor, "UInt", id)
    }
}</pre>
</div>

<div class="ex" id="ExStruct">
<p><a href="#ExStruct">#10</a>: 结构的例子. 把 RECT 结构的地址传递给 GetWindowRect(), 它会把 窗口的左, 上, 右和下边的位置(相对于屏幕) 存入结构的成员中.</p>
<pre>运行 "Notepad"
等待窗口 "无标题 - 记事本"  <em>; 这里同时设置了 "<a href="../misc/WinTitle.htm#LastFoundWindow">上次找到的窗口</a>" 以用于下面的 窗口存在.</em>
Rect := 创建缓冲区(16)  <em>; RECT 结构由四个 32 位整数组成(即 4*4=16).</em>
动态库调用(&quot;GetWindowRect&quot;, &quot;Ptr&quot;, 窗口存在(), &quot;Ptr&quot;, Rect)  <em>; 窗口存在 返回 HWND.</em>
L := 获取数值(Rect, 0, "Int"), T := 获取数值(Rect, 4, "Int")
R := 获取数值(Rect, 8, "Int"), B := 获取数值(Rect, 12, "Int")
信息框 格式化字符串("Left {1} Top {2} Right {3} Bottom {4}", L, T, R, B)</pre>

<div class="ex" id="ExStructRect">
<p><a href="#ExStructRect">#11</a>: 结构的例子. 把 RECT 结构的地址传递给 FillRect(), 这个结构表示需要临时描绘为红色的屏幕区域.</p>
<pre>Rect := 创建缓冲区(16)  <em>; 设置容量为 4 个 4 字节整型.</em>
<a href="NumPut.htm">置数值</a>( "Int", 0                  <em>; 左</em>
      , "Int", 0                  <em>; 上</em>
      , "Int", A_ScreenWidth//2   <em>; 右</em>
      , "Int", A_ScreenHeight//2  <em>; 下</em>
      , Rect)
hDC := 动态库调用(&quot;GetDC&quot;, "Ptr", 0, "Ptr")  <em>; 传递零来获取桌面的设备上下文.</em>
hBrush := 动态库调用(&quot;CreateSolidBrush&quot;, "UInt", 0x0000FF, "Ptr")  <em>; 创建红色画刷(0x0000FF 是 BGR 格式).</em>
动态库调用(&quot;FillRect&quot;, "Ptr", hDC, "Ptr", Rect, "Ptr", hBrush)  <em>; 使用上面的画刷填充指定的矩形.</em>
动态库调用(&quot;ReleaseDC&quot;, "Ptr", 0, "Ptr", hDC)  <em>; 清理.</em>
动态库调用(&quot;DeleteObject&quot;, "Ptr", hBrush)  <em>; 清理.</em></pre>
</div>

<div class="ex" id="ExSystemTime">
<p><a href="#ExSystemTime">#12</a>: 结构的例子. 将系统的时钟更改为指定的日期和时间. 请注意改变时间为将来的日期可能会导致计划任务提早运行!</p>
<pre>SetSystemTime(&quot;20051008142211&quot;)  <em>; 传递<a href="FileSetTime.htm#YYYYMMDD">时间戳</a>(本地的, 非 UTC).</em>

SetSystemTime(YYYYMMDDHHMISS)
<em>; 设置系统时钟为指定的日期和时间.
; 调用者必须确保传入的参数是有效的日期时间戳
; (本地时间, 非 UTC). 成功时返回非零值, 否则返回零.</em>
{
    <em>; 把参数从本地时间转换为 UTC 以便用于 SetSystemTime().</em>
    UTC_Delta := 取时间差(内_时间, 内_时间UTC, "Seconds")  <em>; 取整后秒数会更精确.</em>
    UTC_Delta := 四舍五入(-UTC_Delta/60)  <em>; 取整到最近的分钟数以确保精度.</em>
    YYYYMMDDHHMISS := 时间偏移(YYYYMMDDHHMISS, UTC_Delta, "Minutes")  <em>; 对本地时间应用偏移来转换到 UTC.</em>

    SystemTime := 创建缓冲区(16)  <em>; 此结构由 8 个 UShort 组成(即 8*2=16).</em>

    <a href="NumPut.htm">置数值</a>( "UShort", 截取字符串(YYYYMMDDHHMISS, 1, 4)  <em>; YYYY(年)</em>
          , "UShort", 截取字符串(YYYYMMDDHHMISS, 5, 2)  <em>; MM(月, 01-12)</em>
          , "UShort", 0                             <em>; Unused(周几)</em>
          , "UShort", 截取字符串(YYYYMMDDHHMISS, 7, 2)  <em>; DD(日)</em>
          , "UShort", 截取字符串(YYYYMMDDHHMISS, 9, 2)  <em>; HH(小时, 00-24)</em>
          , "UShort", 截取字符串(YYYYMMDDHHMISS, 11, 2) <em>; MI(分钟)</em>
          , "UShort", 截取字符串(YYYYMMDDHHMISS, 13, 2) <em>; SS(秒)</em>
          , "UShort", 0                             <em>; Unused(毫秒)</em>
          , SystemTime)

    返回 动态库调用(&quot;SetSystemTime&quot;, &quot;Ptr&quot;, SystemTime)
}</pre>
</div>

<p>更多结构的例子:</p>
<ul>
  <li>请参阅 <a href="../scripts/index.htm#WinLIRC">WinLIRC 客户端脚本</a>演示来学习如何使用 动态库调用 来创建
到 TCP/IP 服务器的连接并从那里接收数据.</li>
  <li>操作系统提供了标准的对话框让用户选取字体和/或颜色或图标.
  这些对话框使用了结构, 并可以在这里找到演示的例子: <a href="https://github.com/majkinetor/mm-autohotkey/tree/master/Dlg">GitHub</a>.</li>
</ul>
</div>

<div class="ex" id="ExTaskbar">
<p><a href="#ExTaskbar">#13</a>: 从任务栏移除活动窗口 3 秒. 可以对比一下<a href="ComCall.htm#ExTaskbar">等同效果的 组件调用 示例</a>.</p>
<pre><em>/*
  Methods in <a href="http://msdn.microsoft.com/en-us/library/bb774652.aspx">ITaskbarList</a>'s VTable:
    IUnknown:
      0 QueryInterface  -- 使用 <a href="ComObjQuery.htm">组件对象查询</a> 代替
      1 AddRef          -- 使用 <a href="ObjAddRef.htm">增加对象引用计数</a> 代替
      2 Release         -- 使用 <a href="ObjAddRef.htm">减少对象引用计数</a> 代替
    ITaskbarList:
      3 HrInit
      4 AddTab
      5 DeleteTab
      6 ActivateTab
      7 SetActiveAlt
*/</em>
IID_ITaskbarList  := "{56FDF342-FD6D-11d0-958A-006097C9A090}"
CLSID_TaskbarList := "{56FDF344-FD6D-11d0-958A-006097C9A090}"

<em>; 创建 TaskbarList 对象.</em>
tbl := <a href="ComObjCreate.htm">组件对象创建</a>(CLSID_TaskbarList, IID_ITaskbarList)

activeHwnd := 窗口存在("A")

动态库调用(vtable(tbl.ptr,3), "ptr", tbl)                     <em>; tbl.<a href="http://msdn.microsoft.com/en-us/library/bb774650.aspx">HrInit</a>()</em>
动态库调用(vtable(tbl.ptr,5), "ptr", tbl, "ptr", activeHwnd)  <em>; tbl.<a href="http://msdn.microsoft.com/en-us/library/bb774648.aspx">DeleteTab</a>(activeHwnd)</em>
等待 3000
动态库调用(vtable(tbl.ptr,4), "ptr", tbl, "ptr", activeHwnd)  <em>; tbl.<a href="http://msdn.microsoft.com/en-us/library/bb774646.aspx">AddTab</a>(activeHwnd)</em>

<em>; 非包装接口指针必须手动释放.</em>
减少对象引用计数(tbl.ptr)

vtable(ptr, n) {
    <em>; 获取数值(ptr, "ptr") 返回对象的虚函数表(简称为 vtable) 的地址
    ; 表达式的其余部分从
    ; vtable 中获取第 n 个函数的地址.</em>
    返回 获取数值(获取数值(ptr, "ptr"), n*内_指针大小, "ptr")
}
</pre>
</div>

</body>
</html>